/*
 * Copyright (c) 2013-2015 Isode Limited.
 * All rights reserved.
 * See the COPYING file for more information.
 */

#include <Swift/QtUI/QtPlainChatView.h>

#include <QDialog>
#include <QFileDialog>
#include <QInputDialog>
#include <QLabel>
#include <QMenu>
#include <QProgressBar>
#include <QPushButton>
#include <QScrollBar>
#include <QTextEdit>
#include <QVBoxLayout>

#include <Swiften/Base/FileSize.h>
#include <Swiften/Base/foreach.h>

#include <Swift/Controllers/UIEvents/JoinMUCUIEvent.h>
#include <Swift/Controllers/UIEvents/UIEventStream.h>

#include <Swift/QtUI/ChatSnippet.h>
#include <Swift/QtUI/QtSwiftUtil.h>
#include <Swift/QtUI/QtUtilities.h>

namespace Swift {

QtPlainChatView::QtPlainChatView(QtChatWindow *window, UIEventStream* eventStream)
: QtChatView(window), window_(window), eventStream_(eventStream), idGenerator_(0) {
	QVBoxLayout* mainLayout = new QVBoxLayout(this);
	mainLayout->setSpacing(0);
	mainLayout->setContentsMargins(0,0,0,0);
	log_ = new LogTextEdit(this);
	log_->setReadOnly(true);
	log_->setAccessibleName(tr("Chat Messages"));
	mainLayout->addWidget(log_);
}

QtPlainChatView::~QtPlainChatView() {
}

QString chatMessageToString(const ChatWindow::ChatMessage& message) {
	QString result;
	foreach (boost::shared_ptr<ChatWindow::ChatMessagePart> part, message.getParts()) {
		boost::shared_ptr<ChatWindow::ChatTextMessagePart> textPart;
		boost::shared_ptr<ChatWindow::ChatURIMessagePart> uriPart;
		boost::shared_ptr<ChatWindow::ChatEmoticonMessagePart> emoticonPart;
		boost::shared_ptr<ChatWindow::ChatHighlightingMessagePart> highlightPart;

		if ((textPart = boost::dynamic_pointer_cast<ChatWindow::ChatTextMessagePart>(part))) {
			QString text = QtUtilities::htmlEscape(P2QSTRING(textPart->text));
			text.replace("\n","<br/>");
			result += text;
			continue;
		}
		if ((uriPart = boost::dynamic_pointer_cast<ChatWindow::ChatURIMessagePart>(part))) {
			QString uri = QtUtilities::htmlEscape(P2QSTRING(uriPart->target));
			result += "<a href='" + uri + "' >" + uri + "</a>";
			continue;
		}
		if ((emoticonPart = boost::dynamic_pointer_cast<ChatWindow::ChatEmoticonMessagePart>(part))) {
			result += P2QSTRING(emoticonPart->alternativeText);
			continue;
		}
		if ((highlightPart = boost::dynamic_pointer_cast<ChatWindow::ChatHighlightingMessagePart>(part))) {
			//FIXME: Maybe do something here. Anything, really.
			continue;
		}
	}
	return result;
}

std::string QtPlainChatView::addMessage(const ChatWindow::ChatMessage& message, const std::string& senderName, bool senderIsSelf, boost::shared_ptr<SecurityLabel> label, const std::string& /*avatarPath*/, const boost::posix_time::ptime& time, const HighlightAction& /*highlight*/) {
	QString text = "<p>";
	if (label) {
		text += P2QSTRING(label->getLabel()) + "<br/>";
	}
	QString name = senderIsSelf ? "you" : P2QSTRING(senderName);
	text += QString(tr("At %1 %2 said:")).arg(ChatSnippet::timeToEscapedString(B2QDATE(time))).arg(name) + "<br/>";
	text += chatMessageToString(message);
	text += "</p>";
	log_->append(text);
	const std::string idx = senderIsSelf ? "" : senderName;
	lastMessageLabel_[idx] = label;
	return idx;
}

std::string QtPlainChatView::addAction(const ChatWindow::ChatMessage& message, const std::string& senderName, bool senderIsSelf, boost::shared_ptr<SecurityLabel> label, const std::string& /*avatarPath*/, const boost::posix_time::ptime& time, const HighlightAction& /*highlight*/) {
	QString text = "<p>";
	if (label) {
		text += P2QSTRING(label->getLabel()) + "<br/>";
	}
	QString name = senderIsSelf ? "you" : P2QSTRING(senderName);
	text += QString(tr("At %1 <i>%2 ")).arg(ChatSnippet::timeToEscapedString(B2QDATE(time))).arg(name);
	text += chatMessageToString(message);
	text += "</i></p>";
	log_->append(text);
	const std::string idx = senderIsSelf ? "" : senderName;
	lastMessageLabel_[idx] = label;
	return idx;
}

std::string QtPlainChatView::addSystemMessage(const ChatWindow::ChatMessage& message, ChatWindow::Direction /*direction*/)
{
	QString text = "<p><i>";
	text += chatMessageToString(message);
	text += "</i></p>";
	log_->append(text);
	return "";
}

void QtPlainChatView::addPresenceMessage(const ChatWindow::ChatMessage& message, ChatWindow::Direction /*direction*/)
{
	QString text = "<p><i>";
	text += chatMessageToString(message);
	text += "</i></p>";
	log_->append(text);
}

void QtPlainChatView::addErrorMessage(const ChatWindow::ChatMessage& message)
{
	QString text = "<p><i>";
	text += chatMessageToString(message);
	text += "</i></p>";
	log_->append(text);
}

void QtPlainChatView::replaceMessage(const ChatWindow::ChatMessage& message, const std::string& id, const boost::posix_time::ptime& time, const HighlightAction& /*highlight*/)
{
	QString text = "<p>";
	if (lastMessageLabel_[id]) {
		text += P2QSTRING(lastMessageLabel_[id]->getLabel()) + "<br/>";
	}
	QString name = id.empty() ? "you" : P2QSTRING(id);
	text += QString(tr("At %1 %2 corrected the last message to:")).arg(ChatSnippet::timeToEscapedString(B2QDATE(time))).arg(name) + "<br/>";
	text += chatMessageToString(message);
	text += "</p>";
	log_->append(text);
}

void QtPlainChatView::replaceWithAction(const ChatWindow::ChatMessage& message, const std::string& id, const boost::posix_time::ptime& time, const HighlightAction& /*highlight*/)
{
	QString text = "<p>";
	if (lastMessageLabel_[id]) {
		text += P2QSTRING(lastMessageLabel_[id]->getLabel()) + "<br/>";
	}
	QString name = id.empty() ? "you" : P2QSTRING(id);
	text += QString(tr("At %1 %2 corrected the last action to: <i>")).arg(ChatSnippet::timeToEscapedString(B2QDATE(time))).arg(name);
	text += chatMessageToString(message);
	text += "</i></p>";
	log_->append(text);
}

void QtPlainChatView::replaceLastMessage(const ChatWindow::ChatMessage& message, const ChatWindow::TimestampBehaviour /*timestampBehaviour*/)
{
	QString text = "<p>The last message was corrected to:<br/>";
	text += chatMessageToString(message);
	text += "</p>";
	log_->append(text);
}

void QtPlainChatView::replaceSystemMessage(const ChatWindow::ChatMessage& message, const std::string& /*id*/, const ChatWindow::TimestampBehaviour /*timestampBehaviour*/) {
	QString text = "<p><i>";
	text += chatMessageToString(message);
	text += "</i></p>";
	log_->append(text);
}

void QtPlainChatView::setAckState(const std::string& /*id*/, ChatWindow::AckState state)
{
	if (state == ChatWindow::Failed) {
		addSystemMessage(ChatWindow::ChatMessage("Message delivery failed due to disconnection from server."), ChatWindow::DefaultDirection);
	}
}

std::string QtPlainChatView::addFileTransfer(const std::string& senderName, bool senderIsSelf, const std::string& filename, const boost::uintmax_t sizeInBytes, const std::string& description)
{
	const std::string ftId = "ft" + boost::lexical_cast<std::string>(idGenerator_++);
	const std::string sizeString = formatSize(sizeInBytes);

	FileTransfer* transfer;
	if (senderIsSelf) {
		QString description = QInputDialog::getText(this, tr("File transfer description"),
			tr("Description:"), QLineEdit::Normal, "");
		/* NOTE: it is not possible to abort if description is not provided, since we must always return a valid transfer id */
		const std::string descriptionMessage = description.isEmpty() ? "" : (" \"" + Q2PSTRING(description) + "\"");
		const std::string message = std::string() + "Confirm file transfer: <i>" + filename + " (" + sizeString + " bytes)</i>" + descriptionMessage;
		transfer = new FileTransfer(this, senderIsSelf, ftId, filename, ChatWindow::WaitingForAccept, Q2PSTRING(description), message, true);
		addSystemMessage(ChatWindow::ChatMessage("Preparing to start file transfer..."), ChatWindow::DefaultDirection);
	} else { /* incoming transfer */
		const std::string descriptionMessage = description.empty() ? "" : (" \"" + description + "\"");
		const std::string message = std::string() + "Incoming file transfer: <i>" + filename + " (" + sizeString + " bytes)</i>" + descriptionMessage;
		transfer = new FileTransfer(this, senderIsSelf, ftId, filename, ChatWindow::WaitingForAccept, "", message, true);
		addSystemMessage("Incoming file transfer from " + senderName + "...", ChatWindow::DefaultDirection);
	}

	fileTransfers_[ftId] = transfer;
	layout()->addWidget(transfer->dialog_);

	return ftId;
}

void QtPlainChatView::setFileTransferProgress(std::string id, const int percentageDone)
{
	FileTransferMap::iterator transfer = fileTransfers_.find(id);
	if (transfer != fileTransfers_.end()) {
		transfer->second->bar_->setValue(percentageDone);
	}
}

void QtPlainChatView::setFileTransferStatus(std::string id, const ChatWindow::FileTransferState state, const std::string& msg)
{
	FileTransferMap::iterator transferIter = fileTransfers_.find(id);
	if (transferIter == fileTransfers_.end()) {
		return;
	}

	/* store the layout index so we can restore it to the same location */
	FileTransfer* oldTransfer = transferIter->second;
	const int layoutIndex = layout()->indexOf(oldTransfer->dialog_);
	layout()->removeWidget(oldTransfer->dialog_);
	const std::string &label = (!msg.empty() ? msg : oldTransfer->message_);
	FileTransfer* transfer = new FileTransfer(this, oldTransfer->senderIsSelf_, oldTransfer->ftId_, oldTransfer->filename_, state, oldTransfer->description_, label, false);
	fileTransfers_[oldTransfer->ftId_] = transfer; /* replace the transfer object for this file id */
	delete oldTransfer;

	/* insert the new dialog at the old position in the layout list */
	QBoxLayout* parentLayout = dynamic_cast<QBoxLayout*>(layout());
	assert(parentLayout);
	parentLayout->insertWidget(layoutIndex, transfer->dialog_);

	/* log the transfer end result as a system message */
	if (state == ChatWindow::Finished) {
		addSystemMessage(ChatWindow::ChatMessage("The file transfer completed successfully."), ChatWindow::DefaultDirection);
	} else if (state == ChatWindow::Canceled) {
		addSystemMessage(ChatWindow::ChatMessage("The file transfer was canceled."), ChatWindow::DefaultDirection);
	} else if (state == ChatWindow::FTFailed) {
		addSystemMessage(ChatWindow::ChatMessage("The file transfer failed."), ChatWindow::DefaultDirection);
	}
}

void QtPlainChatView::addMUCInvitation(const std::string& senderName, const JID& jid, const std::string& /*reason*/, const std::string& password, bool /*direct*/, bool isImpromptu, bool isContinuation)
{
	PopupDialog *invite = new PopupDialog(this);

	QLabel* statusLabel = new QLabel;
	std::string msg = senderName + " has invited you to join " + jid.toString() + "...";
	statusLabel->setText(P2QSTRING(msg));
	invite->layout_->addWidget(statusLabel);
	invite->layout_->addWidget(new QLabel); /* padding */

	AcceptMUCInviteAction* accept = new AcceptMUCInviteAction(invite, "Accept", jid, senderName, password, isImpromptu, isContinuation);
	connect(accept, SIGNAL(clicked()), SLOT(acceptMUCInvite()));
	invite->layout_->addWidget(accept);

	AcceptMUCInviteAction* reject = new AcceptMUCInviteAction(invite, "Reject", jid, senderName, password, isImpromptu, isContinuation);
	connect(reject, SIGNAL(clicked()), SLOT(rejectMUCInvite()));
	invite->layout_->addWidget(reject);
	statusLabel->setText(P2QSTRING(msg));

	layout()->addWidget(invite->dialog_);

	addSystemMessage(ChatWindow::ChatMessage(msg), ChatWindow::DefaultDirection);
}

void QtPlainChatView::scrollToBottom()
{
	log_->ensureCursorVisible();
	log_->verticalScrollBar()->setValue(log_->verticalScrollBar()->maximum());
}

void QtPlainChatView::fileTransferAccept()
{
	FileTransfer::Action* action = dynamic_cast<FileTransfer::Action*>(sender());
	if (!action) {
		return;
	}

	FileTransferMap::iterator transferIter = fileTransfers_.find(action->id_);
	if (transferIter == fileTransfers_.end()) {
		return;
	}

	FileTransfer* transfer = transferIter->second;

	const std::string message = transfer->message_;

	if (transfer->senderIsSelf_) { /* if we are the sender, kick of the transfer */
		window_->onFileTransferStart(transfer->ftId_, transfer->description_);
	} else { /* ask the user where to save the file first */
		QString path = QFileDialog::getSaveFileName(this, tr("Save File"), P2QSTRING(transfer->filename_));
		if (path.isEmpty()) {
			fileTransferReject(); /* because the user did not select a desintation path */
			return;
		}
		window_->onFileTransferAccept(transfer->ftId_, Q2PSTRING(path));
	}

	setFileTransferStatus(transfer->ftId_, ChatWindow::Negotiating, message);
}

void QtPlainChatView::fileTransferReject()
{
	FileTransfer::Action* action = dynamic_cast<FileTransfer::Action*>(sender());
	if (action) {
		window_->onFileTransferCancel(action->id_);
		fileTransferFinish();
	}
}

void QtPlainChatView::fileTransferFinish()
{
	FileTransfer::Action* action = dynamic_cast<FileTransfer::Action*>(sender());
	if (action) {
		FileTransferMap::iterator transferIter = fileTransfers_.find(action->id_);
		if (transferIter != fileTransfers_.end()) {
			delete transferIter->second; /* cause the dialog to close */
			fileTransfers_.erase(transferIter);
		}
	}
}

void QtPlainChatView::acceptMUCInvite()
{
	AcceptMUCInviteAction *action = dynamic_cast<AcceptMUCInviteAction*>(sender());
	if (action) {
		eventStream_->send(boost::make_shared<JoinMUCUIEvent>(action->jid_.toString(), action->password_, boost::optional<std::string>(), false, false, action->isImpromptu_, action->isContinuation_));
		delete action->parent_;
	}
}

void QtPlainChatView::rejectMUCInvite()
{
	AcceptMUCInviteAction *action = dynamic_cast<AcceptMUCInviteAction*>(sender());
	if (action) {
		/* NOTE: no action required to reject an invite? */
		delete action->parent_;
	}
}

QtPlainChatView::FileTransfer::FileTransfer(QtPlainChatView* parent, bool senderIsSelf, const std::string& ftId, const std::string& filename, const ChatWindow::FileTransferState state, const std::string &desc, const std::string& msg, bool initializing)
: PopupDialog(parent), bar_(0), senderIsSelf_(senderIsSelf), ftId_(ftId), filename_(filename), description_(desc), message_(msg), initializing_(initializing)
{
	QHBoxLayout* layout = new QHBoxLayout;
	QLabel* statusLabel = new QLabel;
	layout_->addWidget(statusLabel);
	layout_->addWidget(new QLabel); /* padding */

	if (initializing_) {
		FileTransfer::Action* accept = new FileTransfer::Action(senderIsSelf?"Confirm":"Accept", ftId);
		parent->connect(accept, SIGNAL(clicked()), SLOT(fileTransferAccept()));
		layout_->addWidget(accept);
		FileTransfer::Action* reject = new FileTransfer::Action(senderIsSelf?"Cancel":"Reject", ftId);
		parent->connect(reject, SIGNAL(clicked()), SLOT(fileTransferReject()));
		layout_->addWidget(reject);
		statusLabel->setText(P2QSTRING(msg));
		return;
	}

	std::string status = msg;

	switch (state) {
		case ChatWindow::Initialisation: {
			status = "Preparing to send <i>"+ filename + "</i>...";
			FileTransfer::Action* cancel = new FileTransfer::Action("Cancel", ftId);
			parent->connect(cancel, SIGNAL(clicked()), SLOT(fileTransferReject()));
			layout_->addWidget(cancel);
			break;
		}
		case ChatWindow::WaitingForAccept: {
			status = "Waiting for user to accept <i>" + filename + "</i>...";
			FileTransfer::Action* cancel = new FileTransfer::Action("Cancel", ftId);
			parent->connect(cancel, SIGNAL(clicked()), SLOT(fileTransferReject()));
			layout_->addWidget(cancel);
			break;
		}
		case ChatWindow::Negotiating: {
			status = "Preparing to transfer <i>" + filename + "</i>...";
			FileTransfer::Action* cancel = new FileTransfer::Action("Cancel", ftId);
			parent->connect(cancel, SIGNAL(clicked()), SLOT(fileTransferReject()));
			layout_->addWidget(cancel);
			break;
		}
		case ChatWindow::Transferring: {
			status = "Transferring <i>" + filename + "</i>...";
			bar_ = new QProgressBar;
			bar_->setRange(0, 100);
			bar_->setValue(0);
			layout->addWidget(bar_);
			FileTransfer::Action* cancel = new FileTransfer::Action("Cancel", ftId);
			parent->connect(cancel, SIGNAL(clicked()), SLOT(fileTransferReject()));
			layout_->addWidget(cancel);
			break;
		}
		case ChatWindow::Canceled: {
			status = "File <i>" + filename + "</i> was canceled.";
			FileTransfer::Action* finish = new FileTransfer::Action("Hide", ftId);
			parent->connect(finish, SIGNAL(clicked()), SLOT(fileTransferFinish()));
			layout_->addWidget(finish);
			break;
		}
		case ChatWindow::Finished: {
			status = "File <i>" + filename + "</i> was transfered successfully.";
			FileTransfer::Action* finish = new FileTransfer::Action("Hide", ftId);
			parent->connect(finish, SIGNAL(clicked()), SLOT(fileTransferFinish()));
			layout_->addWidget(finish);
			break;
		}
		case ChatWindow::FTFailed: {
			status = "File transfer failed: <i>" + filename + "</i>";
			FileTransfer::Action* finish = new FileTransfer::Action("Hide", ftId);
			parent->connect(finish, SIGNAL(clicked()), SLOT(fileTransferFinish()));
			layout_->addWidget(finish);
			break;
		}
	}

	statusLabel->setText(P2QSTRING(status));
}

void QtPlainChatView::LogTextEdit::contextMenuEvent(QContextMenuEvent *event)
{
	QMenu *menu = createStandardContextMenu();
	menu->exec(event->globalPos());
	delete menu;
}

}
